var util = require('util');
var fs = require('fs');

function ImportVCard(file) {

	var symb = ["BEGIN:VCARD", 
				"VERSION:3.0", 
				"N", 
				"FN", 
				"ORG", 
				"TITLE", 
				"TEL;TYPE=WORK", 
				"EMAIL;TYPE=PREF,INTERNET", 
				"END:VCARD"];
				
	//Read file
	var data = fs.readFileSync(file, 'utf8');
	var input = data.split('\r\n');
			
	if(input.length !== symb.length) {
		throw new Error("File doesn't contain all the requested informations.");
	}
	
	//Create contact
	var contact = new CWContact();
	var line = "";
	
	for(var i=0, length=input.length; i<length ; i++) {
	
		switch(i) {
		
			case 0:
			case 1:
			case 8:
				if(input[i] !== symb[i]) { //Header, Version, Footer
					throw new Error("File has to be in format VCARD.");
				}
			break;
			
			case 2:
			case 3:
			case 6:
			case 7:
				
				line = input[i].split(":");
				
				if(line[0] !== symb[i]) {
					throw new Error("File has to be in format VCARD.");
				}
				else {
					switch(i) {
						case 2:
							contact.name(line[1]);
						break;
						
						case 3:
							contact.fullname(line[1]);
							contact.firstname(line[1].replace(contact.name(),""));
							contact.firstname().replace(" ","");
						break;
						
						case 6:
							contact.mail(line[1]);
						break;
						
						case 7:
							contact.tel(line[1]);
						break;
					}
				}
				
			break;
			
			case 4:
			case 5:
				
				line = input[i].split(":");
				if(line[0] !== symb[i]) {
					throw new Error("File has to be in format VCARD. Symbol " + line[0] + " is not defined.");
				}
				else {
					switch(i) {
						case 4: 
							contact.organisation(line[1].split(";"));
						break;
						case 5:
							contact.title(line[1].split(";"));
						break;
					}
				}
				
			break;
		
		}
	}
	
	console.log("Contact successfully imported !");

	return contact;
}

function ExportCSV(file, contact) {

	if (contact === null) {
		throw new Error("The exported contact can not be null.");
	}
	
	if (!(contact instanceof CWContact)) {
		throw new TypeError("The exported contact must be a CWContact");
	}
	
	var string = contact.ToStringCSV();
	
	fs.writeFile(file, string, function (error) {
		if (error) {
			throw error;
		}
		else {
			console.log("Contact successfully exported !");
		}
	});
}

function areArraysEqual(a, b) {
	if (a === b) {
		return true;
	}
	if (a == null || b == null) {
		return false;
	}
	if (a.length != b.length) {
		return false;
	}
	a.sort();
	b.sort();

	for (var i = 0; i < a.length; ++i) {
		if (a[i] !== b[i]) { //Stop immediately after the first inequality
			return false;
		}
	}

	return true;
}

/**
 * Domain class. Contains all the necessary information to create/update/delete/export a contact.
 *
 * @constructor
 */
function CWContact () {
    var defaultValues;

    defaultValues = {
        name:         '',
		firstname:	  '',
        fullname:     '',
        address:      '',
        organisation: [],
        title:        [],
        mail:         '',
        tel:          ''
    };

    this._name         = defaultValues.name;
	this._firstname    = defaultValues.firstname;
    this._fullname     = defaultValues.fullname;
    this._address      = defaultValues.address;
    this._organisation = defaultValues.organisation;
    this._title        = defaultValues.title;
    this._mail         = defaultValues.mail;
    this._tel          = defaultValues.tel;
}

/**
 * Getter/Setter for a CWContact name.
 *
 * @param name When provided, this method sets name as the new CWContact instance name.
 * @returns {*} Returns the CWContact instance the _name attribute refers to when a name is provided.
 * When it is not, it directly returns the instance name value.
 */
CWContact.prototype.name = function (name) {
    if (name === undefined) {
        return this._name;
    }

    if (name && typeof name !== 'string') {
        throw new TypeError('Name must be a string.');
    }

    this._name = name;

    return this;
};

/**
 * Getter/Setter for a CWContact firstname.
 *
 * @param firstname When provided, this method sets firstname as the new CWContact instance firstname.
 * @returns {*} Returns the CWContact instance the _firstname attribute refers to when a firstname is provided.
 * When it is not, it directly returns the instance firstname value.
 */
CWContact.prototype.firstname = function (firstname) {
    if (firstname === undefined) {
        return this._firstname;
    }

    if (firstname && typeof firstname !== 'string') {
        throw new TypeError('Firstname must be a string.');
    }

    this._firstname = firstname;

    return this;
};

/**
 * Getter/Setter for a CWContact fullname.
 *
 * @param fullname When provided, this method sets fullname as the new CWContact instance fullname.
 * @returns {*} Returns the CWContact instance the _fullname attribute refers to when a fullname is provided.
 * When it is not, it directly returns the instance fullname value.
 */
CWContact.prototype.fullname = function (fullname) {
    if (fullname=== undefined) {
        return this._fullname;
    }

    if (fullname && typeof fullname !== 'string') {
        throw new TypeError('Fullname must be a string.');
    }

    this._fullname = fullname;

    return this;
};

/**
 * Getter/Setter for a CWContact address.
 *
 * @param address When provided, this method sets address as the new CWContact instance address.
 * @returns {*} Returns the CWContact instance the _address attribute refers to when an address is provided.
 * When it is not, it directly returns the instance address value.
 */
CWContact.prototype.address = function (address) {
    if (address === undefined) {
        return this._address;
    }

    if (address && typeof address !== 'string') {
        throw new TypeError('Address must be a string.');
    }

    this._address = address;

    return this;
};

/**
 * Getter/Setter for a CWContact organisation.
 *
 * @param organisation When provided, this method sets organisation as the new CWContact instance organisation.
 * @returns {*} Returns the CWContact instance the _organisation attribute refers to when an organisation is provided.
 * When it is not, it directly returns the instance organisation value.
 */
CWContact.prototype.organisation = function (organisation) {
    if (organisation === undefined) {
        return this._organisation;
    }

    if (!util.isArray(organisation)) {
        throw new TypeError('Organisation must be an array.');
    }

    this._organisation = organisation;

    return this;
};

/**
 * Getter/Setter for a CWContact title.
 *
 * @param title When provided, this method sets title as the new CWContact instance title .
 * @returns {*} Returns the CWContact instance the _title attribute refers to when a title is provided.
 * When it is not, it directly returns the instance title value.
 */
CWContact.prototype.title = function (title) {
    if (title=== undefined) {
        return this._title;
    }

    if (!util.isArray(title)) {
        throw new TypeError('Title must be an array.');
    }

    this._title= title;

    return this;
};

/**
 * Getter/Setter for a CWContact mail.
 *
 * @param mail When provided, this method sets mail as the new CWContact instance mail.
 * @returns {*} Returns the CWContact instance the _mail attribute refers to when a mail is provided.
 * When it is not, it directly returns the instance mail value.
 */
CWContact.prototype.mail = function (mail) {
    if (mail === undefined) {
        return this._mail;
    }

    if (mail && typeof mail !== 'string') {
        throw new TypeError('Mail must be a string.');
    }

    this._mail = mail;

    return this;
};

/**
 * Getter/Setter for a CWContact tel.
 *
 * @param tel When provided, this method sets tel as the new CWContact instance tel.
 * @returns {*} Returns the CWContact instance the _tel attribute refers to when a tel is provided.
 * When it is not, it directly returns the instance tel value.
 */
CWContact.prototype.tel = function (tel) {
    if (tel === undefined) {
        return this._tel;
    }

    if (tel && typeof tel !== 'string') {
        throw new TypeError('Tel must be a string.');
    }

    this._tel = tel;

    return this;
};

/**
 * Merge this CWContact with another one.
 *
 * @param otherContact an instance of CWContact
 * @returns if merge is a success returns true, else returns false
 */
CWContact.prototype.merge = function(otherContact) {

	var success = false;
 	
	if (otherContact === null) {
		throw new Error("The merged contact can not be null.");
	}
	
	if (!(otherContact instanceof CWContact)) {
		throw new TypeError("The merged contact must be a CWContact");
	}

	if(this.name() === otherContact.name()) { //Pour que la fusion ait lieu, l'objet doit être un CWContact et les noms doivent être identiques
		
		var temp = "";		
		if(this.address() !== otherContact.address()) {
			
			temp = this.address();
			this.address(temp + " " + otherContact.address());
		}
	   
		if(this.tel() !== otherContact.tel()) {
	   
	   		temp = this.tel();
			this.tel(temp + " " + otherContact.tel());
		}
	   
		if(this.mail() !== otherContact.mail()) {
	   
			temp = this.mail();
			this.mail(temp += " " + otherContact.mail());
		}
	   
		for(var i=0, length=otherContact.organisation().length; i<length; i++) { //Parcours le tableau des organisations de l'objet à fusionner
			if(this.organisation().indexOf(otherContact.organisation()[i]) === -1) { //Si le tableau de l'objet actuel ne contient pas déjà l'élément, on l'ajoute à la fin.
				this.organisation().push(otherContact.organisation()[i]);
			}
		}
		
		for(var i=0, length=otherContact.title().length; i<length; i++) { //Parcours le tableau des fonctions de l'objet à fusionner
			if(this.title().indexOf(otherContact.title()[i]) === -1) { //Si le tableau de l'objet actuel ne contient pas déjà l'élément, on l'ajoute à la fin.
					this.title().push(otherContact.title()[i]);
			}
		}
	   
		success = true;
	}

	return success;
};

/**
 * Compare this CWContact with another one.
 *
 * @param otherContact an instance of CWContact, comparisonMode a boolean equals to false if we're searching the differences and true if we're searching the similarities. 
 * @returns the list of differences or similarities depending of comparisonMode parameter.
 */
CWContact.prototype.compare = function(otherContact, comparisonMode) {
	
	if (otherContact === null) {
		throw new Error("The merged contact can not be null.");
	}
	
	if (!(otherContact instanceof CWContact)) {
		throw new TypeError("The merged contact must be a CWContact");
	}
	
	if((comparisonMode !== true) && (comparisonMode !== false)) {
		comparisonMode = true; //give the equalities by default
	}
	
	var differences = [];

	//Name's tests
	if(this.name() !== otherContact.name()) {
		if(!comparisonMode) {
			differences.push("name");
		}
	}
	else {
		if(comparisonMode) {
			differences.push("name");
		}
	}
	
	//Address's tests
	if(this.address() !== otherContact.address()) {
		if(!comparisonMode) {
			differences.push("address");
		}
	}
	else {
		if(comparisonMode) {
			differences.push("address");
		}
	}

	//Tel's tests
	if(this.tel() !== otherContact.tel()) {
		if(!comparisonMode) {
			differences.push("tel");
		}
	}
	else {
		if(comparisonMode) {
			differences.push("tel");
		}
	}

	//Mail's tests
	if(this.mail() !== otherContact.mail()) {
		if(!comparisonMode) {
			differences.push("mail");
		}
	}
	else {
		if(comparisonMode) {
			differences.push("mail");
		}
	}

	// Title's tests
	if(this.title().length !== otherContact.title().length) {
		if(!comparisonMode) {
			differences.push("title");
		}
	}
	else {
		
		if(areArraysEqual(this.title(), otherContact.title())) {
		
			if(comparisonMode) {
				differences.push("title");
			}
		}
		else {
			
			if(!comparisonMode) {
				differences.push("title");
			}
		}
	}
	
	//Organisation's tests
	if(this.organisation().length !== otherContact.organisation().length) {
		
		if(!comparisonMode) {
			differences.push("organisation");
		}
	}
	else {
		if(areArraysEqual(this.organisation(), otherContact.organisation())) {
		
			if(comparisonMode) {
				differences.push("organisation");
			}
		}
		else {
			
			if(!comparisonMode) {
				differences.push("organisation");
			}
		}
	}
   
	return differences;
};

/**
 * Convert this CWContact into a string in CSV format.
 * CSV Format : "name;firstname;organisation(s);title(s);tel;mail"
 * @returns a string with CSV format
*/
CWContact.prototype.ToStringCSV = function() {

	var string = "";
	string += this.name() + ";";
	string += this.firstname() + ";";

	for(var i=0,length=this.organisation().length;i<length;i++) {
		
		string += this.organisation()[i];
		
		if(i<(length-1)) { //if current organisation is not the last one, add an internal separator
			string += ",";
		}
		else { //else add an external separator
			string += ";";
		}
	}
	 
	for(var i=0,length=this.title().length;i<length;i++) {
		
		string += this.title()[i];
		
		if(i<(length-1)) { //if current organisation is not the last one, add an internal separator 
			string += ",";
		}
		else { //else add an external separator
			string += ";";
		}
	}
	
	string += this.tel() + ";";
	string += this.mail();
	
	return string;
}

module.exports.CWContact = CWContact;
module.exports.ImportVCard = ImportVCard;
module.exports.ExportCSV = ExportCSV;